﻿using Microsoft.EntityFrameworkCore;
using Chromely.Core.Configuration;
using Chromely.Core.Logging;
using Reader.Core.Application.Models;
using Reader.Core.DataAccessObject;
using Reader.Core.DataAccessObject.Models;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using Reader.Core.Shell32;
using System.Diagnostics;

namespace Reader.Core.Application
{
    public class Books
    {
        private readonly ReaderConfiguration _config;
        private readonly string _basePath;
        private const string _baseBookPath = "books";
        private readonly Applications app;

        public Books(IChromelyConfiguration config)
        {
            _config = config as ReaderConfiguration;
            _basePath = _config.AppExeLocation;
            app = new Applications(config);
            if (!Directory.Exists(Path.Combine(_basePath, _baseBookPath)))
            {
                Directory.CreateDirectory(Path.Combine(_basePath, _baseBookPath));
            }
        }
        private static bool HasDownload(string path)
        {
            var download = false;
            if (Directory.Exists(path))
            {
                if(Directory.GetFiles(path,"*.*", SearchOption.AllDirectories).Length > 0)
                {
                    download = true;
                }
            }
            return download;
        }
        private readonly Func<string, bool> Func1 = HasDownload;
        public async Task<dynamic> GetBookAsync(int id)
        {
            using var db = new ApplicationDBContext();
            var book = await db.Books
                .Include(b => b.Site)
                .Select(b => new
                {
                    BookId = b.Id,
                    Id = b.SiteId,
                    b.Site.Name,
                    b.Url,
                    b.Site.BasicUrl,
                    BookName = b.Name
                })
                .SingleOrDefaultAsync(b => b.BookId == id);
            if (book != null)
            {
                return ResultStatus<dynamic>.Success(book);
            }
            else
            {
                return ResultStatus.Fail();
            }
        }
        public async Task<dynamic> GetBookUpdateAsync(int id)
        {
            using var db = new ApplicationDBContext();
            var book = await db.Books
                .Include(b => b.Site)
                .SingleOrDefaultAsync(b => b.Id == id);
            if(book != null)
            {
                var localPath = $"{_basePath}{book.LocalPath}";
                var indexFile = await File.ReadAllTextAsync($"{localPath}\\index.html");
                var list = Regex.Matches(indexFile, @"<li><a href=""(\d+).html"" data-url=""([^""]*)"">([^<]*)</a></li>", RegexOptions.IgnoreCase | RegexOptions.Multiline);
                if(list != null && list.Count > 0)
                {
                    var last = list.Last();
                    var obj = new
                    {
                        book.Id,
                        book.Name,
                        book.Url,
                        Site = new
                        {
                            book.Site.Id,
                            book.Site.Name,
                            book.Site.BasicUrl
                        },
                        Last = new
                        {
                            Id = last.Groups[1].Value.ToInt32(),
                            Url = last.Groups[2].Value,
                            Name = last.Groups[3].Value
                        },
                        DownloadCompleted = 0,
                        TotalChapter = 0
                    };
                    return ResultStatus<dynamic>.Success(obj);
                }
            }
            return ResultStatus<dynamic>.Fail();
        }
        public async Task<Result> AllBooksAsync()
        {
            using var db = new ApplicationDBContext();
            var books = await db.Books
                .Include(b => b.Author)
                .Include(b => b.Site)
                .Include(b => b.BookShelf)
                .OrderByDescending(b => b.CreateDateTime)
                .Select(b => new AllBookModel
                {
                    Id = b.Id,
                    Name = b.Name,
                    SiteId = b.Site.Id,
                    SiteName = b.Site.Name,
                    SiteUrl = b.Url,
                    ShelfId = b.BookShelf.Id,
                    ShelfName = b.BookShelf.Name,
                    AuthorId = b.Author.Id,
                    AuthorName = b.Author.Name,
                    Url = b.ReadPosition.IsNullOrWhiteSpace() ? $"{b.LocalPath}/index.html" : b.ReadPosition,
                    Completed = b.Completed,
                    Cover = b.Cover,
                    Intro = b.Intro,
                    Download = Func1($"{_basePath}{b.LocalPath}")
                }).ToListAsync();
            return ResultStatus<List<AllBookModel>>.Success(books);
        }
        public async Task<Result<int>> NewBookAsync(NewBookModel newBook)
        {
            if (!newBook.ShelfId.HasValue)
            {
                newBook.ShelfId = 1;
            }
            if (!newBook.Completed.HasValue)
            {
                newBook.Completed = false;
            }
            string path = Path.Combine(_baseBookPath, Guid.NewGuid().ToString()).Replace("\\", "/");
            if (path.PadLeft(1) != "/")
            {
                path = "/" + path;
            }
            newBook.Path = path;
            var author = await app.Authors.GetAuthorAsync(newBook.Author);
            using var db = new ApplicationDBContext();
            var book = await db.Books.SingleOrDefaultAsync(b => b.Name == newBook.Name && b.SiteId == newBook.Site.Id);
            string localPath = localPath = $"{_basePath}{path.Replace("/", @"\")}";
            if(book != null)
            {
                localPath = $"{_basePath}{book.LocalPath.Replace("/", @"\")}";
                newBook.Path = book.LocalPath;
            }
            if (!Directory.Exists(localPath))
            {
                Directory.CreateDirectory(localPath);
            }
            if (!newBook.Cover.IsNullOrWhiteSpace())
            {
                var filename = Path.GetFileName(newBook.Cover);
                if (!File.Exists(Path.Combine(localPath, filename)))
                {
                    var result = await app.Net.DownloadStreamToFileAsync(newBook.Cover, localPath);
                    if (result.Success)
                    {
                        newBook.Cover = Path.Combine(newBook.Path, filename).Replace("\\", "/");
                        if(book != null)
                        {
                            book.Cover = newBook.Cover;
                        }
                    }
                }
            }
            if (book == null)
            {
                book = new Book
                {
                    Name = newBook.Name,
                    Cover = newBook.Cover,
                    Intro = newBook.Intro,
                    SiteId = newBook.Site.Id,
                    Completed = newBook.Completed.HasValue ? newBook.Completed.Value : false,
                    AuthorId = author.Id,
                    ShelfId = newBook.ShelfId.Value,
                    Url = newBook.Site.Url,
                    LocalPath = newBook.Path,
                    CreateDateTime = DateTime.Now
                };
                await db.Books.AddAsync(book);
            }
            
            try
            {
                book.Intro = newBook.Intro;
                book.Completed = newBook.Completed.HasValue ? newBook.Completed.Value : false;
                await CreateIndexAsync(newBook);
                await db.SaveChangesAsync();
                _config.JavaScriptExecutor.ExecuteScript("main", "location.reload()");
                return ResultStatus<int>.Success(book.Id);
            }
            catch (Exception ex)
            {
                Logger.Instance.Log.Error(ex);
                return ResultStatus<int>.Fail(0, ex.Message);
            }
        }
        private async Task CreateIndexAsync(NewBookModel book)
        {
            var indexTemplate = await Utility.GetEmbedStringSourceAsync("index.html");
            indexTemplate = indexTemplate
                .Replace("{Name}", book.Name)
                .Replace("{Site}", book.Site.Name)
                .Replace("{Cover}", book.Cover)
                .Replace("{Intro}", book.Intro)
                .Replace("{Completed}", book.Completed.Value ? "已完本" : "连载中");
            var panel = Regex.Match(indexTemplate, @"\s{4}<div class=""panel panel-default center-block tile"">([.\s\S]*?)\n\s{4}</div>", RegexOptions.IgnoreCase).Value;
            var index = 1;
            var panelTemp = string.Empty;
            foreach (var v in book.Volumes)
            {
                var li = (new Regex("(<li>[^\n]*)")).Match(indexTemplate).Value;
                var liTemp = string.Empty;
                foreach (var chapter in book.Chapters.Where(c => c.VolumeId == v.Id))
                {
                    chapter.Id = index;
                    liTemp += li
                        .Replace("{chapterId}", index.ToString())
                        .Replace("{chapter}", chapter.Name)
                        .Replace("{chapterUrl}", chapter.Url);
                    index++;
                }
                panelTemp += panel.Replace("{Volume}", v.Name).Replace(li, liTemp);
            }
            indexTemplate = indexTemplate.Replace(panel, panelTemp);
            var indexWritePath = $"{_basePath}{Path.Combine(book.Path, "index.html")}";
            File.WriteAllText(indexWritePath, indexTemplate, Encoding.UTF8);
        }
        [DllImport("user32.dll")]
        private static extern bool FlashWindow(IntPtr hWnd, bool bInvert);
        public async Task<Result> CreateChapterAsync(CreateChapterModel model)
        {
            using var db = new ApplicationDBContext();
            var book = await db.Books
                .SingleOrDefaultAsync(b => b.Id == model.BookId);
            if(book != null)
            {
                var localPath = $"{_basePath}{book.LocalPath}";
                var indexFile = await File.ReadAllTextAsync($"{localPath}\\index.html");
                var list = Regex.Matches(indexFile, @"<li><a href=""(\d+).html"" data-url=""([^""]*)"">([^<]*)</a></li>", RegexOptions.IgnoreCase | RegexOptions.Multiline);
                var match = list.SingleOrDefault(m => m.Groups[2].Value == model.Url && m.Groups[3].Value == model.Name);
                if (match != null && match.Success)
                {
                    var id = match.Groups[1].Value.ToInt32();
                    model.Current = id;
                    var url = match.Groups[2].Value;
                    var name = match.Groups[3].Value;
                    var chapterTemplate = await Utility.GetEmbedStringSourceAsync("chapter.html");
                    var lines = model.Content.Split(new char[] { '\n' });
                    var content = string.Empty;
                    foreach (var line in lines)
                    {
                        if (!line.Trim().IsNullOrWhiteSpace())
                        {
                            content += $"<p>{line.Trim().Replace("&nbsp;", "")}</p>";
                        }
                    }
                    if (!File.Exists($"{localPath}\\.{model.Current}.html") || model.Force)
                    {
                        var template = chapterTemplate
                                    .Replace("{Name}", model.Name)
                                    .Replace("{Content}", content)
                                    .Replace("{ChapterUrl}", model.Url)
                                    .Replace("{Progress}", $"{model.Current * 100.00m / model.Total:0.00}%")
                                    .Replace("{Prev}", $"{model.Prev}")
                                    .Replace("{Next}", $"{model.Next}");
                        File.WriteAllText($"{localPath}\\{id}.html", template, Encoding.UTF8);
                    }
                }
            }
            
            if (_config.Platform == Chromely.Core.ChromelyPlatform.Windows)
            {
                if (model.Current + 1 < model.Total)
                {
                    TaskbarManager.SetProgressValue(model.Current, model.Total);
                }
                else
                {
                    TaskbarManager.SetProgressState(TaskbarProgressBarState.NoProgress);
                    var handler = Process.GetCurrentProcess().MainWindowHandle;
                    FlashWindow(handler, true);
                }
            }
            return ResultStatus.Success();
        }
        public async Task<Result> UpdateBookAsync(CreateChapterModel model)
        {
            return ResultStatus<dynamic>.Fail();
            //
            // TODO: 更新小说
            //
            using var db = new ApplicationDBContext();
            var book = await db.Books
                .SingleOrDefaultAsync(b => b.Id == model.BookId);
            if (book != null)
            {
                var localPath = $"{_basePath}{book.LocalPath}";
                var indexFile = await File.ReadAllTextAsync($"{localPath}\\index.html");
                var list = Regex.Matches(indexFile, @"<li><a href=""(\d+).html"" data-url=""([^""]*)"">([^<]*)</a></li>", RegexOptions.IgnoreCase | RegexOptions.Multiline);
                var match = list.SingleOrDefault(m => m.Groups[2].Value == model.Url && m.Groups[3].Value == model.Name);
                if (match != null && match.Success)
                {
                    var id = match.Groups[1].Value.ToInt32();
                    model.Current = id;
                    var url = match.Groups[2].Value;
                    var name = match.Groups[3].Value;
                    var chapterTemplate = await Utility.GetEmbedStringSourceAsync("chapter.html");
                    var lines = model.Content.Split(new char[] { '\n' });
                    var content = string.Empty;
                    foreach (var line in lines)
                    {
                        if (!line.Trim().IsNullOrWhiteSpace())
                        {
                            content += $"<p>{line.Trim().Replace("&nbsp;", "")}</p>";
                        }
                    }
                    if (!File.Exists($"{localPath}\\.{model.Current}.html") || model.Force)
                    {
                        var template = chapterTemplate
                                    .Replace("{Name}", model.Name)
                                    .Replace("{Content}", content)
                                    .Replace("{ChapterUrl}", model.Url)
                                    .Replace("{Progress}", $"{model.Current * 100.00m / model.Total:0.00}%")
                                    .Replace("{Prev}", $"{model.Prev}")
                                    .Replace("{Next}", $"{model.Next}");
                        File.WriteAllText($"{localPath}\\{id}.html", template, Encoding.UTF8);
                    }
                }
            }

            if (_config.Platform == Chromely.Core.ChromelyPlatform.Windows)
            {
                if (model.Current + 1 < model.Total)
                {
                    TaskbarManager.SetProgressValue(model.Current, model.Total);
                }
                else
                {
                    TaskbarManager.SetProgressState(TaskbarProgressBarState.NoProgress);
                    var handler = Process.GetCurrentProcess().MainWindowHandle;
                    FlashWindow(handler, true);
                }
            }
            return ResultStatus.Success();
        }
        public async Task DownloadFile(int id)
        {
            using var db = new ApplicationDBContext();
            var book = await db.Books
                .Include(b => b.Site)
                .Select(b => new
                {
                    b.Id,
                    SiteName = b.Site.Name,
                    b.Name,
                    b.LocalPath
                })
                .SingleOrDefaultAsync(b => b.Id == id);
            if(book != null)
            {
                var localPath = $"{_basePath}{book.LocalPath}";
                var indexFile = await File.ReadAllTextAsync($"{localPath}\\index.html");
                var list = Regex.Matches(indexFile, @"<li><a href=""(\d+).html"" data-url=""([^""]*)"">([^<]*)</a></li>", RegexOptions.IgnoreCase | RegexOptions.Multiline);
                string text = string.Empty;
                List<ExportModel> models = new List<ExportModel>();
                foreach(Match m in list)
                {
                    if (m.Success)
                    {
                        models.Add(new ExportModel
                        {
                            Id = m.Groups[1].Value.ToInt32(),
                            Name = m.Groups[3].Value,
                            Content = string.Empty
                        });
                        //text += $"{m.Groups[3].Value}{Environment.NewLine}{Environment.NewLine}";
                        //var chapterFile = await File.ReadAllTextAsync($"{localPath}\\{m.Groups[1].Value}.html");
                        //var ps = Regex.Matches(chapterFile, @"<p>([^<]*)</p>", RegexOptions.IgnoreCase | RegexOptions.Multiline);
                        //foreach(Match p in ps)
                        //{
                        //    if (p.Success)
                        //    {
                        //        text += $"　　{p.Groups[1].Value}{Environment.NewLine}";
                        //    }
                        //}
                        //TaskbarManager.SetProgressValue(m.Groups[1].Value.ToInt32(), list.Count);
                    }
                }
                int i = 0;
                Parallel.ForEach(models, new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount }, (it) =>
                {
                    var chapterFile = File.ReadAllText($"{localPath}\\{it.Id}.html");
                    var ps = Regex.Matches(chapterFile, @"<p>([^<]*)</p>", RegexOptions.IgnoreCase | RegexOptions.Multiline);
                    foreach(Match p in ps)
                    {
                        if (p.Success)
                        {
                            it.Content += $"　　{p.Groups[1].Value}{Environment.NewLine}";
                        }
                    }
                    i++;
                    TaskbarManager.SetProgressValue(i, models.Count);
                });
                foreach(var model in models)
                {
                    text += $"{model.Name}{Environment.NewLine}{Environment.NewLine}{model.Content}{Environment.NewLine}";
                }
                await File.WriteAllTextAsync($"{_basePath}\\{_baseBookPath}\\{book.Name}.txt", text, Encoding.UTF8);
                _config.JavaScriptExecutor.ExecuteScript($"msg.show('保存在：{_basePath.Replace("\\", "/")}{_baseBookPath}/{book.Name}.txt');");
                TaskbarManager.SetProgressState(TaskbarProgressBarState.NoProgress);
                var handler = Process.GetCurrentProcess().MainWindowHandle;
                FlashWindow(handler, true);
            }
        }
        public async Task<List<BookInfoSource>> GetBookInfoSourceAsync()
        {
            using var db = new ApplicationDBContext();
            var sources = await db.BookInfoSources.ToListAsync();
            return sources;
        }
        public async Task<Result> RemoveBook(int bookId)
        {
            using var db = new ApplicationDBContext();
            var book = await db.Books.SingleOrDefaultAsync(b => b.Id == bookId);
            if(book != null)
            {
                var path = $"{_basePath}{book.LocalPath.Replace("/", @"\")}";
                if (Directory.Exists(path))
                {
                    Directory.Delete(path, true);
                }
                db.Books.Remove(book);
                await db.SaveChangesAsync();
                return ResultStatus.Success();
            }
            else
            {
                return ResultStatus.Fail("找不到小说");
            }
        }
        public async Task Access(string url)
        {
            var match = Regex.Match(url, "(/books/[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12})/\\d+.html");
            if (match.Success)
            {
                using var db = new ApplicationDBContext();
                var book = await db.Books.SingleOrDefaultAsync(b => b.LocalPath == match.Groups[1].Value);
                if(book != null)
                {
                    book.ReadPosition = match.Value;
                    await db.SaveChangesAsync();
                }
            }
        }
        public async Task<string> GetReadPosition(int id)
        {
            using var db = new ApplicationDBContext();
            var book = await db.Books.SingleOrDefaultAsync(b => b.Id == id);
            if(book != null)
            {
                return book.ReadPosition.IsNullOrWhiteSpace() ? $"'{book.LocalPath}/index.html'" : $"'{book.ReadPosition}'";
            }
            else
            {
                _config.JavaScriptExecutor.ExecuteScript("msgbox('错误','获取小说失败。');");
                return string.Empty;
            }
        }
    }
}
